﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Internal;
using System.Diagnostics;
using System.Threading;
using System.Globalization;

namespace System.ComponentModel.Composition
{
    public class MutableExportProvider : ExportProvider, IDisposable
    {
        private List<Tuple<ComposablePart, Export>> _partsAndExports = new List<Tuple<ComposablePart, Export>>();
        private CompositionBatch _currentComposablePartBatch;
        private volatile bool _isDisposed = false;
        private volatile bool _isRunning = false;
        private volatile bool _isComposing = false;
        private Lock _lock = new Lock();
        private EventHandler<ExportsChangedEventArgs> _changedEventStorage;
        private ExportProvider _sourceProvider;
        private CompositionEngine _compositionEngine;

        /// <summary>
        /// Initializes a new instance of the <see cref="MutableExportProvider"/> class.
        /// </summary>
        public MutableExportProvider()
        {
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!this._isDisposed)
                {
                    bool disposeLock = false;
                    CompositionEngine compositionEngine = null;
                    try
                    {
                        using (new WriteLock(this._lock))
                        {
                            if (!this._isDisposed)
                            {
                                compositionEngine = this._compositionEngine;
                                this._compositionEngine = null;
                                this._sourceProvider = null;
                                this._isDisposed = true;
                                disposeLock = true;
                            }
                        }
                    }
                    finally
                    {
                        if (compositionEngine != null)
                        {
                            compositionEngine.Dispose();
                        }

                        if (disposeLock)
                        {
                            this._lock.Dispose();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the source provider.
        /// </summary>
        /// <value>The source provider.</value>
        public ExportProvider SourceProvider
        {
            get
            {
                this.ThrowIfDisposed();

                return this._sourceProvider;
            }
            set
            {
                this.ThrowIfDisposed();

                Requires.NotNull(value, "value");
                using (new WriteLock(this._lock))
                {
                    this.EnsureCanSet(this._sourceProvider);
                    this._sourceProvider = value;
                }

                // This should be safe to do outside the lock, because only the first setter will ever win
                // and others will throw
                CompositionEngine compositionEngine = new CompositionEngine();
                compositionEngine.SourceProvider = _sourceProvider;
                Thread.MemoryBarrier();
                this._compositionEngine = compositionEngine;
            }
        }

        /// <summary>
        /// Returns all exports that match the conditions of the specified import.
        /// </summary>
        /// <param name="importDefinition">The <see cref="ImportDefinition"/> that defines the conditions of the
        /// <see cref="Export"/> to get.</param>
        /// <returns></returns>
        /// <result>
        /// An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match
        /// the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an
        /// empty <see cref="IEnumerable{T}"/>.
        /// </result>
        /// <remarks>
        /// 	<note type="inheritinfo">
        /// The implementers should not treat the cardinality-related mismatches as errors, and are not
        /// expected to throw exceptions in those cases.
        /// For instance, if the import requests exactly one export and the provider has no matching exports or more than one,
        /// it should return an empty <see cref="IEnumerable{T}"/> of <see cref="Export"/>.
        /// </note>
        /// </remarks>
        protected override IEnumerable<Export> GetExportsCore(ImportDefinition importDefinition)
        {
            Requires.NotNull(importDefinition, "import");
            this.ThrowIfDisposed();
            this.EnsureRunning();

            // We never change the list of parts and exports in place, but rather copy, change and write.
            // therefore all we need to do here is to read the member
            List<Tuple<ComposablePart, Export>> partsAndExports = null;
            using (new ReadLock(this._lock))
            {
                partsAndExports = this._partsAndExports;
            }

            if (partsAndExports.Count == 0)
            {
                return Enumerable.Empty<Export>();
            }

            Func<ExportDefinition, bool> constraint = importDefinition.Constraint.Compile();
            return partsAndExports.Select(node => node.Second)
                               .Where(export => constraint(export.Definition));
        }

        /// <summary>
        /// Occurs when the exports in the <see cref="ExportProvider"/> change.
        /// </summary>
        /// <remarks>
        /// The <see cref="ExportsChanged"/> event passes an <see cref="ExportsChangedEventArgs"/>
        /// which provides a list of contract names for the exports that have changed. An export
        /// is considered to have changed if it has been added, removed, or updated in the
        /// <see cref="ExportProvider"/>.
        /// </remarks>
        public override event EventHandler<ExportsChangedEventArgs> ExportsChanged
        {
            add
            {
                using (new WriteLock(this._lock))
                {
                    this._changedEventStorage += value;
                }
            }
            remove
            {
                using (new WriteLock(this._lock))
                {
                    this._changedEventStorage -= value;
                }
            }
        }

        /// <summary>
        /// Raises the <see cref="E:ExportsChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.ComponentModel.Composition.ExportsChangedEventArgs"/> instance containing the event data.</param>
        protected override void OnExportsChanged(ExportsChangedEventArgs e)
        {
            EventHandler<ExportsChangedEventArgs> changedEvent = null;

            using (new ReadLock(this._lock))
            {
                changedEvent = this._changedEventStorage;
            }

            if (changedEvent != null)
            {
                CompositionResult result = CompositionServices.TryFire(changedEvent, this, e);
                result.ThrowOnErrors();
            }
        }

        public bool IsComposing
        {
            get
            {
                this.ThrowIfDisposed();
                this.EnsureRunning();

                return this._isComposing;
            }
        }

        public bool IsCompositionPending
        {
            get 
            {
                this.ThrowIfDisposed();
                this.EnsureRunning();

                return !this.IsComposing && (this._currentComposablePartBatch != null); 
            }
        }


        public void AddPart(ComposablePart part)
        {
            Requires.NotNull(part, "part");

            this.ThrowIfDisposed();
            this.EnsureRunning();

            using (new WriteLock(this._lock))
            {
                this.EnsureBatch();
                this._currentComposablePartBatch = this._currentComposablePartBatch.Add(part);
            }
        }


        public void RemovePart(ComposablePart part)
        {
            Requires.NotNull(part, "part");

            this.ThrowIfDisposed();
            this.EnsureRunning();

            using (new WriteLock(this._lock))
            {
                this.EnsureBatch();
                this._currentComposablePartBatch = this._currentComposablePartBatch.Remove(part);
            }
        }

        public void Compose()
        {
            this.ThrowIfDisposed();
            this.EnsureRunning();

            bool wasComposing = this._isComposing;
            if (!wasComposing)
            {
                using (new WriteLock(this._lock))
                {
                    wasComposing = this._isComposing;
                    if (!wasComposing)
                    {
                        this._isComposing = true;
                    }
                }
            }

            if (wasComposing)
            {
                throw new InvalidOperationException(Strings.InvalidOperationReentrantCompose);
            }

            try
            {
                CompositionResult result = this.ComposeCore();
                result.ThrowOnErrors();
            }
            finally
            {
                using(new WriteLock(this._lock))
                {
                    this._isComposing = false;
                }
            }
        }

        private CompositionResult ComposeCore()
        {
            Assumes.IsTrue(!this._isDisposed);

            CompositionResult result = CompositionResult.SucceededResult;
            int numberOfIterations = 0;

            while (numberOfIterations < CompositionEngine.MaximumNumberOfCompositionIterations)
            {
                // Check if we have any batch to process
                CompositionBatch currentBatch = null;
                using (new WriteLock(this._lock))
                {
                    currentBatch = this._currentComposablePartBatch;
                    this._currentComposablePartBatch = null;
                }

                if (currentBatch == null)
                {
                    break;
                }

                // Register Exports
                // - Unregister any removed component parts
                // - Register any added component parts (i.e. add composable exports)
                // - Recompose any imports effected by the these changes
                result = result.MergeResult(this.TryRegisterExports(currentBatch));

                // Satisfy Imports
                // - Satisfy imports on all newly added component parts
                result = result.MergeResult(this.TrySatisfyImports(currentBatch));

                // loop to run anything postponed.
                numberOfIterations++;
            }

            if (numberOfIterations >= CompositionEngine.MaximumNumberOfCompositionIterations)
            {
                return result.MergeIssue(CompositionIssue.CreateIssueAsError(
                    CompositionIssueId.ComposeTookTooManyIterations,
                    Strings.ComposeTookTooManyIterations,
                    CompositionEngine.MaximumNumberOfCompositionIterations));
            }

            return result;
        }

        private CompositionResult TryRegisterExports(CompositionBatch batch)
        {
            Assumes.NotNull(batch);

            CompositionResult result = CompositionResult.SucceededResult;

            // Copy the current list of parts and exports - we are about to modify it
            // This is an OK thing to do as we only this method can modify the List AND Compose can
            // only be executed on one thread at a time - thus two different threads cannot tramp over each other
            List<Tuple<ComposablePart, Export>> partsAndExports = null;
            using (new ReadLock(this._lock))
            {
                partsAndExports = this._partsAndExports.ToList(); // this copies the list
            }

            // Unregister any removed component parts
            foreach (ComposablePart part in batch.PartsToRemove)
            {
                for (int i = 0; i < partsAndExports.Count; i++)
                {
                    while (i < partsAndExports.Count && partsAndExports[i].First == part)
                    {
                        partsAndExports.RemoveAt(i);
                    }
                }
                this._compositionEngine.UnregisterForRecomposition(part);
            }

            // Register any added component parts (i.e. add lazy exports)
            foreach (ComposablePart part in batch.PartsToAdd)
            {
                // NOTE : this used to handle the case of adding a factory type is a hacky fashion.
                // We can no longer create support factories here as we have no access to definitions
                // If we chose to  support that again, we should support adding definitions to the container.
                foreach (ExportDefinition definition in part.ExportDefinitions)
                {
                    // NOTE : these are necessary, otherwise we will carry the iteration variables inside the closure below
                    // and it will always use the last export and the last part
                    ComposablePart currentPart = part;
                    ExportDefinition currentDefinition = definition;

                    Export containerComposedExport = new Export(
                        currentDefinition,
                        () =>
                        {
                            // This is needed as the export can live longer than this class. Unlikely but possible.
                            this.ThrowIfDisposed();

                            this._compositionEngine.SatisfyImportsAndRegisterForRecomposition(currentPart).ThrowOnErrors();
                            return currentPart.GetExportedObject(currentDefinition);
                        });

                    partsAndExports.Add(new Tuple<ComposablePart, Export>(currentPart, containerComposedExport));
                }
            }

            // Recompose any imports effected by the these changes
            IEnumerable<string> changedContractNames = batch.PartsToAdd.Concat(batch.PartsToRemove).
                SelectMany(part => part.ExportDefinitions).
                Select(exportDefinition => exportDefinition.ContractName).
                Distinct().ToArray();

            // now replace the old partsAndExports
            using (new WriteLock(this._lock))
            {
                this._partsAndExports = partsAndExports;
            }

            // at this point the changes are observable through GetExports, thus we can fire the event
            result = result.MergeResult(
                CompositionServices.TryInvoke(() => this.OnExportsChanged(new ExportsChangedEventArgs(changedContractNames)))
                );

            return result;
        }

        private CompositionResult TrySatisfyImports(CompositionBatch batch)
        {
            Assumes.NotNull(batch);
            var result = CompositionResult.SucceededResult;

            foreach (ComposablePart part in batch.PartsToAdd)
            {
                result = result.MergeResult(this._compositionEngine.SatisfyImportsAndRegisterForRecomposition(part));
            }

            return result;
        }

        // This is always called under a write lock
        private void EnsureBatch()
        {
            if (this._currentComposablePartBatch == null)
            {
                this._currentComposablePartBatch = new CompositionBatch();
            }
        }


        [DebuggerStepThrough]
        private void ThrowIfDisposed()
        {
            if (this._isDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
        }

        [DebuggerStepThrough]
        private void EnsureCanRun()
        {
            if ((this._sourceProvider == null) || (this._compositionEngine == null))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Strings.ObjectMustBeInitialized, "SourceProvider")); // NOLOC
            }
        }

        [DebuggerStepThrough]
        private void EnsureRunning()
        {
            if (!this._isRunning)
            {
                using (new WriteLock(this._lock))
                {
                    if (!this._isRunning)
                    {
                        this.EnsureCanRun();
                        this._isRunning = true;
                    }
                }
            }
        }

        [DebuggerStepThrough]
        private void EnsureCanSet<T>(T currentValue)
            where T : class
        {
            if ((this._isRunning) || (currentValue != null))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Strings.ObjectAlreadyInitialized));
            }
        }

    }
}
